/**
 * 
 */
package junior.db.dao.base;

import junior.db.dao.enums.LikeMode;
import junior.db.dao.enums.Operator;
import junior.db.query.QueryHelper;
import junior.util.ArrayUtils;
import org.springframework.util.Assert;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Map;

/**
 * @author bo.wu
 * @CreateDate 2014年7月10日
 */
public class Filter implements IFilterItem {
    
    public static Filter and() {
        return new Filter(Operator.AND, false, false);
    }
    
    public static Filter or() {
        return new Filter(Operator.OR, false, false);
    }
    
    public static Filter leftBracket() {
        return new Filter(Operator.LEFT_BRACKET, true, false);
    }
    
    public static Filter rightBracket() {
        return new Filter(Operator.RIGHT_BRACKET, false, true);
    }
    
    public static Filter eq(String left, Object value) {
        return new Filter(left, Operator.EQ, value);
    }
    
    public static Filter neq(String left, Object value) {
        return new Filter(left, Operator.NEQ, value);
    }
    
    public static Filter gt(String left, Object value) {
        return new Filter(left, Operator.GT, value);
    }
    
    public static Filter lt(String left, Object value) {
        return new Filter(left, Operator.LT, value);
    }
    
    public static Filter gte(String left, Object value) {
        return new Filter(left, Operator.GTE, value);
    }
    
    public static Filter lte(String left, Object value) {
        return new Filter(left, Operator.LTE, value);
    }
    
    public static Filter bt(String left, Object value1, Object value2) {
        return new Filter(left, Operator.BT, new Object[]{value1, value2});
    }
    
    public static Filter nvl(String left) {
        return new Filter(left, Operator.NULL);
    }
    
    public static Filter notNvl(String left) {
        return new Filter(left, Operator.NOT_NULL);
    }
    
    public static Filter in(String left, Collection<?> values) {
        Assert.notEmpty(values, "Parameter values cannot be null or empty");
        
        return in(left, values.toArray());
    }
    
    public static Filter in(String left, Object[] values) {
        Assert.notEmpty(values, "Parameter values cannot be null or empty");
        
        if (values.length == 1) {
            return new Filter(left, Operator.EQ, values[0]);
        }
        return new Filter(left, Operator.IN, values);
    }
    
    public static Filter notIn(String left, Collection<?> values) {
        Assert.notEmpty(values, "Parameter values cannot be null or empty");
        
        return notIn(left, values.toArray());
    }
    
    public static Filter notIn(String left, Object[] values) {
        return new Filter(left, Operator.NOT_IN, values);
    }
    
    public static Filter like(String left, Object value, LikeMode likeMode) {
        Assert.notNull(likeMode, "Parameter likeMode is required");
        if (value == null) return null;
        return new Filter(left, Operator.LIKE, likeMode.wrap(value));
    }
    
    public static Filter notLike(String left, Object value, LikeMode likeMode) {
        Assert.notNull(likeMode, "Parameter likeMode is required");
        if (value == null) return null;
        return new Filter(left, Operator.NOT_LIKE, likeMode.wrap(value));
    }
    
    /**
     * not like左前缀查询，%在右边
     * @param left
     * @param value
     * @return
     */
    public static Filter notLikePrefix(String left, Object value) {
        if (value == null) return null;
        return new Filter(left, Operator.NOT_LIKE, QueryHelper.likeR(value.toString()));
    }
    
    /**
     * not like右后缀查询： %在左边
     * @param left
     * @param value
     * @return
     */
    public static Filter notLikeSuffix(String left, Object value) {
        if (value == null) return null;
        return new Filter(left, Operator.NOT_LIKE, QueryHelper.likeL(value.toString()));
    }
    
    public static Filter clone(Filter filter) {
        Filter clone = new Filter();
        clone.dataType = filter.dataType;
        clone.field = filter.field;
        clone.left = filter.left;
        clone.logic = filter.logic;
        clone.operator = filter.operator;
        clone.opt = filter.opt;
        clone.prefix = filter.prefix;
        clone.requirePostJoinOpt = filter.requirePostJoinOpt;
        clone.requirePreJoinOpt = filter.requirePreJoinOpt;
        clone.right = filter.right;
        clone.type = filter.type;
        clone.sqlValue = filter.sqlValue;
        clone.rightIsExpression = filter.rightIsExpression;
        clone.rightAsField = filter.rightAsField;
        clone.expressionArgs = filter.expressionArgs;
        
        return clone;
    }
    
    private String field;
    private String prefix;
    private String left;
    private Operator operator;
    private Object right;
    private String opt;
    private DataType dataType;
    private String type;
    private boolean logic;
    private boolean requirePreJoinOpt = true; //是否需要前置连接符
    private boolean requirePostJoinOpt = true; //是否需要后置连接符
    private boolean rightIsExpression;
    private SqlValue sqlValue;
    private boolean rightAsField;
    private boolean leftAsExpression;
    private Map<String, Object> expressionArgs;
    
    
    public Filter() {}
    
    public Filter(String left, Operator operator) {
        this(left, operator, null);
    }
    
    public Filter(String left, Operator operator, Object right) {
        int idx = left.indexOf(".");
        if (idx != -1) {
            this.prefix = left.substring(0,  idx + 1);
            this.left = left.substring(idx + 1);
        } else {
            this.left = left;
        }
        this.field = this.left;
        this.operator = operator;
        this.right = right;
    }
    
    public Filter(Operator operator, boolean requirePreJoinOpt, boolean requirePostJoinOpt) {
        this.operator = operator;
        this.logic = true;
        this.requirePreJoinOpt = requirePreJoinOpt;
        this.requirePostJoinOpt = requirePostJoinOpt;
    }
    
    public Filter withRightAsExpression() {
        return withRightAsExpression(null);
    }
    public Filter withRightAsExpression(Map<String, Object> expressionArgs) {
        this.rightAsField = false;
        this.rightIsExpression = true;
        this.expressionArgs = expressionArgs;
        return this;
    }
    
    public SqlValue getRightExpression() {
        return sqlValue;
    }
    
    public boolean getRightIsExpression() {
        return rightIsExpression || isLogic();
    }
    
    public String getLeft() {
        return left == null ? "" : left;
    }
    
    public Operator getOperator() {
        return operator;
    }
    
    public Object getRight() {
        return right;
    }
    
    public void setRight(Object right) {
        this.right = right;
    }
    
    public String getOpt() {
        return opt;
    }
    
    public String getType() {
        return type;
    }
    
    public void setType(String type) {
        if (opt != null) {
            this.type = type;
            this.dataType = DataType.fromString(type);
        }
    }
    
    public DataType getDataType() {
        return dataType;
    }
    
    public void setDataType(DataType dataType) {
        this.dataType = dataType;
    }
    
    public boolean isLogic() {
        return logic;
    }
    
    @Override
    public boolean isNormal() {
        return true;
    }
    
    @Override
    public boolean isRequirePreJoinOpt() {
        return requirePreJoinOpt;
    }
    
    @Override
    public boolean isRequirePostJoinOpt() {
        return requirePostJoinOpt;
    }
    
    public String getPrefix() {
        return prefix != null ? prefix : "";
    }
    
    public Filter withRightAsField() {
        this.rightAsField = true;
        return this;
    }
    
    public boolean isRightAsField() {
        return rightAsField;
    }
    
    public Filter withLeftAsSql() {
        this.leftAsExpression = true;
        return this;
    }
    
    public boolean isLeftAsExpression() {
        return leftAsExpression;
    }
    
    public Map<String, Object> getExpressionArgs() {
        return expressionArgs;
    }
    
    public final String getField() {
        return field;
    }
    
    void setRightIsExpression(SqlValue rightIsExpression) {
        this.right = null;
        this.sqlValue = rightIsExpression;
    }
    
    public String getSql() {
        Object left = getLeft();
        Object right = getRight();
        
        if (left == null) left = "";
        
        if (rightIsExpression) {
            StringBuilder buff = new StringBuilder(left.toString()).append(" ").append(getOperator().getOptString());
            for (SqlValue.Part part : sqlValue.getList()) {
                if (part.isArg()) {
                    buff.append("?");
                } else {
                    buff.append(part.getVal());
                }
            }
            return buff.toString();
        }
        
        if (getOperator() == Operator.BT) {
            return getString(left, " between ? and ?");
        }
        
        if (getOperator() == Operator.IN || getOperator() == Operator.NOT_IN) {
            int length = ((Object[]) right).length;
            String[] params = new String[length];
            Arrays.fill(params, "?");
            return getString(left, " ", getOperator().getOptString(), " (", ArrayUtils.join(params, ",") ,")");
        }
        
        return getString(left, " ", getOperator().getOptString(), (right == null ? "" : "?"));
    }
    
    protected void setLeft(String left) {
        this.left = left;
    }
    
    protected void setPrefix(String prefix) {
        this.prefix = prefix;
    }
    
    @Override
    public String toString() {
        Object left = getLeft();
        Object right = getRight();
        
        if (getOperator() == Operator.BT) {
            Object[] vals = (Object[]) right;
            return getString(getLeft(), " between ", vals[0], " and ", vals[1]);
        }
        return getString(left == null ? "" : getPrefix() + left, getOperator().getOptString(), right == null ? "" : right);
    }
    
    
    private String getString(Object...objects) {
        StringBuilder buff = new StringBuilder();
        for (Object obj :objects) {
            buff.append(obj);
        }
        return buff.toString();
    }
    
    public static enum DataType {
        STRING(String.class, new String[] {"string"}),
        
        BYTE(Byte.class, new String[] {"byte"}),
        LONG(Long.class, new String[] {"long"}),
        SHORT(Short.class, new String[] {"short"}),
        INTEGER(Integer.class, new String[] {"int", "integer"}),
        DOUBLE(Double.class, new String[] {"double"}),
        FLOAT(Float.class, new String[] {"float"}),
        BOOLEAN(Boolean.class, new String[] {"boolean"}),
        
        BYTE_ARRAY(Byte[].class, new String[] {"byte[]"}),
        LONG_ARRAY(Long[].class, new String[] {"long[]"}),
        SHORT_ARRAY(Short[].class, new String[] {"short[]"}),
        INTEGER_ARRAY(Integer[].class, new String[] {"int[]", "integer[]"}),
        DOUBLE_ARRAY(Double[].class, new String[] {"double[]"}),
        FLOAT_ARRAY(Float[].class, new String[] {"float[]"}),
        BOOLEAN_ARRAY(Boolean[].class, new String[] {"boolean[]"}),
        
        _BYTE(byte.class, new String[] {"_byte"}),
        _LONG(long.class, new String[] {"_long"}),
        _SHORT(short.class, new String[] {"_short"}),
        _INT(int.class, new String[] {"_int", "_integer"}),
        _DOUBLE(double.class, new String[] {"_double"}),
        _FLOAT(float.class, new String[] {"_float"}),
        _BOOLEAN(boolean.class, new String[] {"_boolean"}),
        
        _BYTE_ARRAY(byte[].class, new String[] {"_byte[]"}),
        _LONG_ARRAY(long[].class, new String[] {"_long[]"}),
        _SHORT_ARRAY(short[].class, new String[] {"_short[]"}),
        _INT_ARRAY(int[].class, new String[] {"_int[]", "_integer[]"}),
        _DOUBLE_ARRAY(double[].class, new String[] {"_double[]"}),
        _FLOAT_ARRAY(float[].class, new String[] {"_float[]"}),
        _BOOLEAN_ARRAY(boolean[].class, new String[] {"_boolean[]"}),
        
        DATE(Date.class, new String[] {"date"}),
        _DATE(java.sql.Date.class, new String[] {"_date"}),
        _TIME(java.sql.Time.class, new String[] {"_time"}),
        DECIMAL(BigDecimal.class, new String[] {"decimal", "bigdecimal", "bigDecimal"}),
        BIG_INTEGER(BigInteger.class, new String[] {"biginteger", "bigInteger"}),
        ;
        
        private String[] alias;
        private Class<?> typeClass;
        
        private DataType(Class<?> typeClass, String[] alias) {
            this.typeClass = typeClass;
            this.alias = alias;
        }
        
        public Class<?> getTypeClass() {
            return typeClass;
        }
        
        public static DataType fromString(String type) {
            for (DataType dataType : values()) {
                if (dataType.name().equalsIgnoreCase(type)) {
                    return dataType;
                }
                for (String alias : dataType.alias) {
                    if (alias.equalsIgnoreCase(type)) {
                        return dataType;
                    }
                }
            }
            return null;
        }
    }
    
}
